#define _LARGEFILE64_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <SDL/SDL.h>
#include <SDL/SDL_thread.h>
#include <netinet/in.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <syslog.h>
#include <sys/socket.h>
#include <sys/select.h>

#define COMMAND_FCLOSE (1)
#define COMMAND_FSEEK (2)
#define COMMAND_FREAD (3)

#define AVSEEK_SIZE   0x10000

#define NODAEMON

#ifdef NODAEMON
#define PRINTOUT(prio, ...) printf(__VA_ARGS__); printf("\n")
#else
#define PRINTOUT(prio, ...) syslog(prio, __VA_ARGS__)
#endif


char dir_array[200][257]; //200 files, 256 maximal file name length (+1 for '\0')
int dir_num=0;
SDL_mutex *mutex;
SDL_cond *cond;


void handle_error(char *msg) {
    #ifndef NODAEMON
    syslog(LOG_ERR, "%s : %s", msg, strerror(errno));
    #else
    perror(msg);
    #endif
    exit(-1);
}

void directory_scanner(char * dir_name) {
    struct dirent *dir_entries = NULL;
    DIR *dir=NULL;

    while(1) {
        if (-1 == SDL_LockMutex(mutex))
            handle_error("SDL_LockMutex()");
        memset(dir_array, 0, 200*257*sizeof(char));
        dir_num = 0;

        if (NULL == (dir = opendir(dir_name)))
            handle_error("opendir()");

        while (NULL != (dir_entries = readdir(dir))) {
            if ((0 == strcmp(dir_entries->d_name, ".")) ||
               (0 == strcmp(dir_entries->d_name, "..")))
                continue;
            strcpy(dir_array[dir_num], dir_name);
            strcat(dir_array[dir_num], "/");
            strncat(dir_array[dir_num], dir_entries->d_name, 257-strlen(dir_name));
            PRINTOUT(LOG_DEBUG, "directory_scanner()->added: %s",dir_array[dir_num]);
            dir_num++;
        }
        dir_num--;

        if (-1 == SDL_UnlockMutex(mutex))
            handle_error("SDL_UnlockMutex()");
        
        if (-1 == SDL_CondSignal(cond))
            handle_error("directory_scanner()->CondSignal()");

        sleep(5);
    }
}

int my_select(int sock) {
    fd_set readfds;
    struct timeval timeout;
    int status=0;
    
    FD_ZERO(&readfds);
    FD_SET(sock, &readfds);
    timeout.tv_sec = 20;
    timeout.tv_usec = 0;
    for (;;) {
        if (-1 == (status = select(sock+1,
                                        &readfds, NULL, NULL, &timeout))) {
            if (EINTR == errno) continue; /* signal interrupted, ignore */
            handle_error("select()");
        } else {
            if (0 == status) return status; /* if timed-out, return 0 */
            if (!FD_ISSET(sock, &readfds)) return -1; /* if it's not SET then something's wrong */
            return status; /* if we got till here, then everything is ok */
        }
    }
}

void do_read(int sock, FILE* file) {
    char *buf=NULL;
    int ret=0, arg=0, status=0;
    /* get bufsize from client and allocate buffer */
    if (-1 == recv(sock, (void*)&arg, 4, 0))
        handle_error("handle_client()->recv()");
    if (NULL == (buf = (void*)malloc(arg)))
        handle_error("handle_client()->malloc()");

    /* read from file and send to client */
    ret = read(fileno(file), buf, arg);
    if ((-1 == send(sock, (void*)&ret, 4, 0)) ||
        (-1 == ret) ||
        (-1 == (status = send(sock, (void*)buf, ret, 0))))
        handle_error("handle_client()->send()");

    free(buf);
    PRINTOUT(LOG_DEBUG, "handle_client(): got FREAD, len=%d, send=%d, ret=%d",arg, status, ret);
}

void do_seek(int sock, FILE *file) {
    int64_t pos=0, seekretvalue=0;
    int arg=0, retvalue=0;
    struct stat64 filestat;
    /* get parameters from client */
    if ((-1 == recv(sock, (void*)&pos, 8, 0)) ||
        (-1 == recv(sock, (void*)&arg, 4, 0)))
        handle_error("handle_client()->recv()");
    /* handle the AVSEEK_SIZE case (it wants us to return file size) */
    if (AVSEEK_SIZE == arg) {
        fstat64(fileno(file), &filestat);
        seekretvalue = filestat.st_size;
    } else {
        seekretvalue = (int64_t)lseek64(fileno(file), pos, arg);
    }
    /* send answer */
    if (-1 == (retvalue = send(sock, (void*)&seekretvalue, 8, 0)))
        handle_error("handle_client()->send()");
    
    PRINTOUT(LOG_DEBUG, "handle_client(): got FSEEK, pos=%lld, whence=%d, send=%d, ret=%lld",pos,arg, retvalue, seekretvalue);
}

void client_handler(void *arg) {
    int sock = *((int*)arg);
    FILE *file;
    int i=0;
    int status = 0;
    char command=0;
    
    PRINTOUT(LOG_INFO, "Got a new client.");

    if (-1 == SDL_LockMutex(mutex))
        handle_error("client_handler()->SDL_LockMutex()");
    
    i = rand()%(dir_num+1);
    file = fopen(dir_array[i], "rb");
    PRINTOUT(LOG_DEBUG, "I chose this (%d / %d): %s",i, dir_num, dir_array[i]);

    if (-1 == SDL_UnlockMutex(mutex))
        handle_error("client_handler()->SDL_UnlockMutex()");
    
    for (;;) {
        if (-1 == (status = my_select(sock))) {
            handle_error("client_handler()->select()");
        }
        else if (status > 0) {
            /* get command from client */
            if (-1 == (status = recv(sock, &command, 1, 0))) {
                if (104 == errno) {
                    goto handle_client_exit; //Meaning 'connection reset by peer'
                }
                handle_error("handle_client()->recv()");
            } else if (0 == status) goto handle_client_exit; // Meaning client disconnected.

            /* determing what to do according to the command */
            switch (command) {
                case COMMAND_FREAD:
                    do_read(sock, file);
                    break;
                case COMMAND_FSEEK:
                    do_seek(sock, file);
                    break;
                case COMMAND_FCLOSE:
                    PRINTOUT(LOG_DEBUG, "handle_client(): got FCLOSE.");
                    if (0 != fclose(file))
                        handle_error("handle_client()->fclose()");
                    file = NULL;
                    break;
                default:
                    handle_error("handle_client()->recv() got a message out"
                            "-of-protocol");
                    break;
            }
        }
    }
        
    handle_client_exit:
    PRINTOUT(LOG_INFO, "Client disconnected.");
    if (NULL != file) {
        fclose(file);
        file = NULL;
    }
    close(sock);
    return 0;
}

int main(int argc, char** argv) {
    int sock, new_sock, status;
    struct sockaddr_in myAddr, theirAddr;
    unsigned int sin_size = sizeof(struct sockaddr_in);
    int port = 1337;
    srand(time(NULL));
    if (2 != argc) {
        printf("Wrong argument. Usage: %s /path/to/media", argv[0]);
        return (EXIT_FAILURE);
    }

    #ifndef NODAEMON
    /* fork() and check if it worked, and if we're the father then exit. */
    if (0 > (status = fork()))
        handle_error("fork()");
    else if (0 < status)
        exit(0);
    /* now only child remains */
    setsid(); /* obtain a new process group */
    setlogmask (LOG_UPTO (LOG_DEBUG));
    openlog("os_ex2_server", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1);
    syslog(LOG_INFO, "Server up and daemonized. Pid=%d.", getpid());
    #endif

    mutex = SDL_CreateMutex();
    cond = SDL_CreateCond();

    SDL_CreateThread(directory_scanner, (void*)argv[1]);

    if ((-1 == SDL_LockMutex(mutex)) ||
        (-1 == SDL_CondWait(cond, mutex)) ||
        (-1 == SDL_UnlockMutex(mutex)))
        handle_error("main()->mutex/cond.");

    myAddr.sin_family = AF_INET;
    myAddr.sin_port = htons(port);
    myAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    if ((0 > (sock = socket(AF_INET, SOCK_STREAM, 0))) ||
       (-1 == bind(sock, (struct sockaddr *)&myAddr, sizeof(myAddr))) ||
       (-1 == listen(sock, 7)))
        handle_error("main()->error initializing socket");

    for(;;) {
        if (my_select(sock) > 0) { /* then some socket is ready */
            if (0 > (new_sock = accept(sock, (struct sockaddr *)&theirAddr, &sin_size)))
                handle_error("main()->accept()");
            SDL_CreateThread(client_handler, (void*)&new_sock);
        }
    }
    #ifndef NODAEMON
    closelog();
    #endif
    return (EXIT_SUCCESS);
}